﻿using System;
using System.Collections.Concurrent;
using System.Threading;
using Grpc.Net.Client;
using Grpc.Net.ClientFactory;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Options;

namespace K9Nano.RemoteClient.LoadBalancing
{
    internal class RoundRobinGrpcChannelFactory : IDisposable
    {
        private readonly IOptionsMonitor<GrpcClientFactoryOptions> _clientOptionsMonitor;
        private readonly IOptionsMonitor<RemoteClientOptions> _remoteOptionsMonitor;
        private readonly IHttpContextAccessor _httpContextAccessor;

        private readonly ConcurrentDictionary<string, ChannelPool> _pools;

        public RoundRobinGrpcChannelFactory(IOptionsMonitor<GrpcClientFactoryOptions> clientOptionsMonitor,
            IOptionsMonitor<RemoteClientOptions> remoteOptionsMonitor,
            IHttpContextAccessor httpContextAccessor)
        {
            _clientOptionsMonitor = clientOptionsMonitor;
            _remoteOptionsMonitor = remoteOptionsMonitor;
            _httpContextAccessor = httpContextAccessor;
            _pools = new ConcurrentDictionary<string, ChannelPool>();
        }

        public GrpcChannel Create(string name)
        {
            var pool = _pools.GetOrAdd(name, key =>
            {
                var remoteOption = _remoteOptionsMonitor.Get(name);
                var clientOption = _clientOptionsMonitor.Get(name);
                return new ChannelPool(remoteOption.MaxPool, clientOption, _httpContextAccessor);
            });

            return pool.Next();
        }

        public void Dispose()
        {
            foreach (var pool in _pools.Values)
            {
                pool.Dispose();
            }
        }

        private class ChannelPool : IDisposable
        {
            private int _index;

            private readonly GrpcChannel[] _subChannels;

            public ChannelPool(int maxPool, GrpcClientFactoryOptions options, IHttpContextAccessor httpContextAccessor)
            {
                _index = 0;
                _subChannels = new GrpcChannel[maxPool];
                for (var i = 0; i < _subChannels.Length; i++)
                {
                    _subChannels[i] = CreateChannel(options, httpContextAccessor);
                }
            }

            private GrpcChannel CreateChannel(GrpcClientFactoryOptions options, IHttpContextAccessor httpContextAccessor)
            {
                var channelOptions = new GrpcChannelOptions();
                foreach (var action in options.ChannelOptionsActions)
                {
                    action.Invoke(channelOptions);
                }
                channelOptions.HttpClient = null;

                channelOptions.HttpHandler = new BearerHttpMessageHandler(httpContextAccessor);

                var channel = GrpcChannel.ForAddress(options.Address, channelOptions);
                return channel;
            }

            public GrpcChannel Next()
            {
                var nextIndex = Interlocked.Increment(ref _index) % _subChannels.Length;
                return _subChannels[nextIndex];
            }

            public void Dispose()
            {
                for (var i = 0; i < _subChannels.Length; i++)
                {
                    _subChannels[i].Dispose();
                    _subChannels[i] = null;
                }
            }
        }
    }
}